from time import time

from utils.params import Param
from utils.interval import Interval
from utils.misc import INF

from .plugins.limits import Limit


class SolverParam(Param):
    """
    This param subclass simply logs all parameter changes to the solver's info stream. (inspired
    by gurobi).
    """
    def _set(self, paramset, new_value):
        solver = paramset.object
        if self in paramset.values:
            old_value = self.get(paramset)
            solver.log.info("parameter changed <{!r}> {!r} -> {!r}",
                            self.name, old_value, new_value)
        else:
            solver.log.info("parameter initialized <{!r}> := {!r}",
                            self.name, new_value)
        Param._set(self, paramset, new_value)


class SolverParamSet(Param.Set):
    cpu_limit = SolverParam(description="maximum run time allowed",
                            options="a value between 0 and inf",
                            domain=Interval(0, INF),
                            default=INF)

    @cpu_limit.setter
    def cpu_limit(self, limit):
        solver = self.object
        solver.limits.update([Limit.Cpu.abs(limit)])

    @cpu_limit.adapter
    def cpu_limit(self, limit):
        return float(limit)

    # --------------------------------------------------------------------------
    iter_limit = SolverParam(description="maximum number of iterations allowed",
                             options="a value between 0 and inf",
                             domain=Interval(0, INF),
                             default=INF)

    @iter_limit.setter
    def iter_limit(self, limit):
        solver = self.object
        solver.limits.update([Limit.Iters.abs(limit)])

    @iter_limit.adapter
    def iter_limit(self, limit):
        return float(limit)

    # --------------------------------------------------------------------------
    verbosity = SolverParam(description="defines how much output is display by the solver",
                            options="quiet, normal, or verbose",
                            domain=("quiet", "normal", "verbose"),
                            default="normal")

    @verbosity.setter
    def verbosity(self, level):
        solver = self.object
        if level == "quiet":
            # disable everything except the error stream
            solver.log.disable()
            solver.log.error.enable()
        elif level == "normal":
            # enable everything except the debug stream
            solver.log.enable()
            solver.log.debug.disable()
        else:
            # enable everything
            solver.log.enable()

    @verbosity.adapter
    def verbosity(self, level):
        if isinstance(level, int):
            return type(self).verbosity.domain[level]
        return level.strip().lower()

    # --------------------------------------------------------------------------
    seed = SolverParam(description="the seed used by the random number generator",
                       options="anything goes, as long as it is hashable",
                       default=None)

    @seed.setter
    def seed(self, value):
        solver = self.object
        solver.meta.seed = value
        solver.rng.seed(value)

    @seed.adapter
    def seed(self, value):
        if value is None:
            return int(time() * 1000)
        return value if isinstance(value, int) else hash(value)

    # --------------------------------------------------------------------------
    instance_loader = SolverParam(description="function used to load an instance by name",
                                  options="a callable or None",
                                  default=None)

    @instance_loader.getter
    def instance_loader(self):
        solver = self.object
        return solver.instance_loader

    @instance_loader.setter
    def instance_loader(self, loader):
        solver = self.object
        solver.instance_loader = loader

    @instance_loader.adapter
    def instance_loader(self, loader):
        solver = self.object
        if loader is None:
            return type(solver).instance_loader
        if callable(loader):
            return loader
        raise TypeError("instance loader should be callable or None")

    # --------------------------------------------------------------------------
    # cutoff = SolverParam(description="solver discards any solutions worse than this value",
    #                      options="any acceptable bound on the optimal")

    # @cutoff.adapter
    # def cutoff(self, value):
    #     return float(value)


SolverParam.Set = SolverParamSet
